package com.googlecode.yoohoo.io.socket.stream;

import java.util.UUID;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.googlecode.yoohoo.io.socket.AbstractConnectionContext;
import com.googlecode.yoohoo.utils.AppUtils;
import com.googlecode.yoohoo.utils.Constants;
import com.googlecode.yoohoo.xmppcore.protocol.JabberId;
import com.googlecode.yoohoo.xmppcore.protocol.XmppProtocolException;
import com.googlecode.yoohoo.xmppcore.protocol.stream.Stream;
import com.googlecode.yoohoo.xmppcore.protocol.stream.StreamType;
import com.googlecode.yoohoo.xmppcore.protocol.stream.error.HostUnknown;
import com.googlecode.yoohoo.xmppcore.protocol.stream.error.InvalidXml;
import com.googlecode.yoohoo.xmppcore.protocol.stream.error.NotAuthorized;
import com.googlecode.yoohoo.xmppcore.protocol.stream.error.UnsupportedVersion;
import com.googlecode.yoohoo.xmppcore.stream.INegotiationContext;

public class InitialStreamNegotiant extends AbstractStreamNegotiant {
	private static final Log log = LogFactory.getLog(InitialStreamNegotiant.class);
	private static final String XML_DECLARATION_TAG = "<?xml version='1.0'?>";
	private boolean xmlDeclarationTagReceived;
	private IStreamFeaturesManager featuresManager;
	
	public InitialStreamNegotiant(IStreamFeaturesManager featuresManager) {
		xmlDeclarationTagReceived = false;
		this.featuresManager = featuresManager;
	}

	@Override
	protected void doNegotiate(INegotiationContext context, String message) {
		if (handleIntialStreamRequest(context, message))
			nextPhase(context);
	}

	/**
	 * @param context
	 * @param message
	 */
	protected boolean handleIntialStreamRequest(INegotiationContext context, String message) {
		if (log.isTraceEnabled()) {
			log.trace("Received a initial stream request: " + message);
		}
		
		if (!xmlDeclarationTagReceived && message.equals(XML_DECLARATION_TAG)) {
			xmlDeclarationTagReceived = true;
			return false;
		}
		
		try {
			validateInitialStream(context, message);
		} finally {
			// (rfc3290 4.7.1)
			// must response a opening stream
			sendOpeningStream(context);
		}
		
		sendFeatures(context);
		return true;
	}

	protected void sendFeatures(INegotiationContext context) {
		context.write(getFeaturesAdvertisement());
	}
	
	private void sendOpeningStream(INegotiationContext context) {
		Stream stream = new Stream();
		stream.setFrom(new JabberId(AppUtils.getHostName(((AbstractConnectionContext)context).getBundleContext())));
		stream.setVersion(Constants.XMPP_SPECIFICATION_VERSION);
		stream.setId(UUID.randomUUID().toString());
		
		context.write(stream.toXml());
	}

	private void validateInitialStream(INegotiationContext context,
			String message) {
		// must be an initial stream request
		if (!message.startsWith("<stream:stream ")) {
			throw new XmppProtocolException(new NotAuthorized());
		}
		
		Stream stream = new Stream();
		stream.fromXml(message);
		
		// (rfc3920 4.4.1)
		if (stream.getVersion() == null || !Constants.XMPP_SPECIFICATION_VERSION.equals(stream.getVersion())) {
			throw new XmppProtocolException(new UnsupportedVersion());
		}
		
		// attribute 'id' mustn't be set in client:server mode
		if ((stream.getStreamType() == StreamType.CLIENT_TO_SERVER) &&
				stream.getId() != null) {
			throw new XmppProtocolException(new InvalidXml());
		}
		
		JabberId to = stream.getTo();
		if (to.getNode() != null || to.getResource() != null ||
				!AppUtils.getHostName(((AbstractConnectionContext)context).getBundleContext()).
					equals(to.getDomain()))
			throw new XmppProtocolException(new HostUnknown());
	}

	protected String getFeaturesAdvertisement() {
		return featuresManager.getFeaturesAdvertisement(getPhase());
	}
	
	@Override
	protected NegotiationPhase getPhase() {
		return NegotiationPhase.INITIAL_STREAM;
	}
}
